
const { ccclass, property } = cc._decorator;

export interface HotMsg {
    info: string,
    fileLabel: string, // 文件信息
    fileProgress: number, //文件进度
    byteLabel: string, // 字节信息
    byteProgress: number, //字节进度
    version: string,//版本号
}

@ccclass
export class HotUpdate extends cc.Component {

    //本地的manifest
    @property({ type: cc.Asset })
    manifestUrl: cc.Asset = null;

    //本地热更下载资源存储路径
    private _storagePath = '';

    //资源管理器
    private _assetsManager = null;

    //是否正在更新
    private _isUpdating = false;

    //信息
    private _msg: HotMsg = {
        info: '', //文字信息
        fileLabel: '', // 文件信息
        fileProgress: 0, //文件进度
        byteLabel: '', // 字节信息
        byteProgress: 0, //字节进度
        version: '', // 当前版本
    };

    // 外部调用热更结束回调
    private _completeHotUpdateCb: Function = null;
    // 执行时变化回调
    private _upDatingCb: Function = null;

    // start() {
    //     this.checkHotUpdate();
    // }
    onDestroy() {
        this._assetsManager.setEventCallback(null);
    }

    /********************************************************************
     * 外部调用方法
     ********************************************************************/
    // 开启信息监听
    public startListener(cb: Function) {
        this._upDatingCb = cb
        this.node.on('hotUpdate', this._upDatingCb, this)
    }
    // 关闭信息监听
    public offListener() {
        this.node.off('hotUpdate', this._upDatingCb, this)
    }
    // 外部调用检测
    public checkHotUpdate(finishedCb: Function) {

        if (!finishedCb) {
            return;
        }

        this._completeHotUpdateCb = finishedCb;

        //如果是网页,就直接执行游戏
        if (!cc.sys.isNative) {
            this._completeHotUpdateCb();
            return;
        }


        // jsb.fileUtils.getWritablePath()获得可写路径
        this._storagePath = `${(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/')}hot-update-assets/`

        // 获得本地manifest来获得当前版本的所有信息,如果有热更就放到上面定义的文件夹

        this._assetsManager = new jsb.AssetsManager(this.manifestUrl.nativeUrl, this._storagePath, this._versionCompareHandle);
        // 获取本地版本号
        this._msg.version = this._assetsManager.getLocalManifest().getVersion();
        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // 设置最大的并发数是两个
            this._assetsManager.setMaxConcurrentTask(2);
            this._msg.info='Max concurrent tasks count have been limited to 2';
            cc.log(this._msg)
        }

        //检测版本
        this._checkVersion();
    }

    /********************************************************************
     * 私有方法
     ********************************************************************/
    //检测版本
    private _checkVersion() {
        // //没有初始化的时候
        // if (this._assetsManager.getState() === jsb.AssetsManager.State.UNINITED) {
        //     // Resolve md5 url
        //     let url = this.manifestUrl.nativeUrl;
        //     if (cc.loader.md5Pipe) {
        //         url = cc.loader.md5Pipe.transformURL(url);
        //     }
        //     //加载本地的manifest
        //     this._assetsManager.loadLocalManifest(url);
        // }
        // if (!this._assetsManager.getLocalManifest() || !this._assetsManager.getLocalManifest().isLoaded()) {
        //     this._msg.info = 'Failed to load local manifest ...';
        //     return;
        // }

        //设置回调
        this._assetsManager.setEventCallback(this._checkVersionCallback.bind(this));

        this._assetsManager.checkUpdate();
    }

    //版本检测回调
    private _checkVersionCallback(event) {
        cc.log('HotUpdate._checkVersionCallback:Code: ' + event.getEventCode());

        let isNewVersionFound = false;

        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this._msg.info = "No local manifest file found, hot update skipped.";
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this._msg.info = "Fail to download manifest file, hot update skipped.";
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                //版本已是最新
                this._msg.info = "Already up to date with the latest remote version.";
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                //热更环节
                this._msg.info = `New version found, please try to update. ( ${this._assetsManager.getTotalBytes()})`;
                // this._msg.checkBtn.active = false;
                this._msg.fileProgress = 0;
                this._msg.byteProgress = 0;
                isNewVersionFound = true;
                break;
            default:
                return;
        }
        cc.log('我是信息:', this._msg.info)
        this.node.emit('hotUpdate', this._msg)
        //注销回调
        this._assetsManager.setEventCallback(null);

        if (isNewVersionFound) {
            this._updateVersion();
        } else {
            // TODO 没有或出错 待定 进入游戏或报错
            if (this._completeHotUpdateCb) {
                this._completeHotUpdateCb();
            }
        }
    }

    //版本更新
    private _updateVersion() {
        this._assetsManager.setEventCallback(this._updateVersionCallback.bind(this));
        this._isUpdating = true;
        this._assetsManager.update();
    }

    //更新回调
    private _updateVersionCallback(event) {
        cc.log('啊实打实大所大所多', event)
        let needRestart = false;
        let failed = false;
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this._msg.info = 'No local manifest file found, hot update skipped.';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                // 更新进度
                // TODO 进度派发
                this._msg.byteProgress = event.getPercent();
                this._msg.fileProgress = event.getPercentByFile();

                this._msg.fileLabel = event.getDownloadedFiles() + ' / ' + event.getTotalFiles();
                this._msg.byteLabel = event.getDownloadedBytes() + ' / ' + event.getTotalBytes();

                const msg = event.getMessage();
                if (msg) {
                    this._msg.info = 'Updated file: ' + msg;
                    // cc.log(event.getPercent()/100 + '% : ' + msg);
                }
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this._msg.info = 'Fail to download manifest file, hot update skipped.';
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this._msg.info = 'Already up to date with the latest remote version.';
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this._msg.info = 'Update finished. ' + event.getMessage();
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                this._msg.info = 'Update failed. ' + event.getMessage();
                // this._msg.retryBtn = true;
                this._isUpdating = false;
                // this._canRetry = true;
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this._msg.info = 'Asset update error: ' + event.getAssetId() + ', ' + event.getMessage();
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this._msg.info = event.getMessage();
                break;
            default:
                break;
        }
        cc.log(this._msg.info)
        this.node.emit('hotUpdate', this._msg)
        //失败
        if (failed) {
            this._assetsManager.setEventCallback(null);
            this._isUpdating = false;
            // TODO 弹窗通知
        }

        //需要重启
        if (needRestart) {
            this._assetsManager.setEventCallback(null);

            // Prepend the manifest's search path
            let searchPaths = jsb.fileUtils.getSearchPaths();
            let newPaths = this._assetsManager.getLocalManifest().getSearchPaths();

            cc.log(JSON.stringify(newPaths));

            Array.prototype.unshift.apply(searchPaths, newPaths);
            // This value will be retrieved and appended to the default search path during game startup,
            // please refer to samples/js-tests/main.js for detailed usage.
            // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.

            cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);

            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    }

    /**
     * @private 版本比对函数 
     * 传入版本号格式1.0.0
     * 
     * 版本号[0]:大版本,强制更新,下载新包,不能做热更
     * 版本号[1]:向下兼容的功能增加
     * 版本号[2]:向下兼容的bug修复
     * 
     * @param {string} versionA 版本号A
     * @param {string} versionB 版本号B
     * @returns {number}
     * 1.返回值大于0, 版本号A大于版本号B
     * 2.返回值等于0, 版本号A等于版本号B
     * 3.返回值小于0, 版本号A小于版本号B
     * @memberof HotUpdate
     */
    private _versionCompareHandle(versionA: string, versionB: string): number {
        cc.log(`TS Custom Version Compare: version A is ${versionA}, version B is ${versionB}`);
        const vA = versionA.split('.');
        const vB = versionB.split('.');

        //console.assert(vB.length > vA.length,'版本A落后了')

        for (let i = 0; i < vA.length; ++i) {
            const a = parseInt(vA[i]);
            const b = parseInt(vB[i] || '0');
            if (a === b) {
                continue;
            }
            else {
                return a - b;
            }
        }
        if (vB.length > vA.length) {
            return -1;
        }
        else {
            return 0;
        }
    }
}
